#include "executor.h"
#include "shared\stlex.h"
#include <windows.h>

std::vector<exec_context_t*> Executor::m_ctxs;
Executor *Executor::self = NULL;
static CRITICAL_SECTION Executor::cs_cmd;


Executor::Executor()
{
	self = NULL;
	InitializeCriticalSection(&cs_cmd);
}
Executor::~Executor(){
	delete_vector(m_ctxs);
	DeleteCriticalSection(&cs_cmd);
}


#define ERROR_IF_FAIL(result) if(!result) {\
	log(#result);\
	throw("error");\
}
	


//handler_mode == 0 for stdout,
//handler_mode == 1 for stderr
bool process_output(HANDLE hFile,std::string & outbuf,exec_context_t *ctx,int handler_mode)
{
		 char buf[256];
		 
		 DWORD nRead;
		 if(!::ReadFile(hFile,buf,255,&nRead,NULL)){
			 log("ReadFile error %d",GetLastError());
			// res  = GetLastError();
			 return false;
		 }
		 buf[nRead]='\0';
		 outbuf += buf;

		 while(true){

		    int sep_len = 2;

			size_t pos =  outbuf.find("\r\n");
			if(pos == std::string::npos){ 
				sep_len = 1;
				pos =  outbuf.find("\r");
				if(pos == std::string::npos)
					pos = outbuf.find("\n");
				if(pos == std::string::npos)
				break;
			}
			
			std::string subs = outbuf.substr(0,pos+sep_len);
			outbuf = outbuf.substr(pos+sep_len);
			if(handler_mode == 0)
				ctx->parser->on_stdout_line(ctx->thread_id,ctx->param,subs);
			else
				ctx->parser->on_stderr_line(ctx->thread_id,ctx->param,subs);

		 }

		 return true;
}


DWORD WINAPI ConsoleThread(LPVOID param){
	exec_context_t *ctx = (exec_context_t *)param;

	HANDLE hOutRead = NULL,hOutReadTmp = NULL,hOutWrite = NULL;
	HANDLE hErrRead = NULL,hErrReadTmp = NULL,hErrWrite = NULL;
	//start the console program

	HANDLE hInRead = NULL,hInWriteTmp = NULL,hInWrite = NULL;

	SECURITY_ATTRIBUTES sa;	
	sa.nLength= sizeof(SECURITY_ATTRIBUTES);
	sa.lpSecurityDescriptor = NULL;
	sa.bInheritHandle = TRUE;


	ERROR_IF_FAIL(::CreatePipe(&hOutReadTmp,&hOutWrite,&sa,0));
	ERROR_IF_FAIL(::CreatePipe(&hErrReadTmp,&hErrWrite,&sa,0));
	//this will error 6=INVALID_HANDLE_VALUE
	/*if(!::SetHandleInformation(&hReadTmp,HANDLE_FLAG_INHERIT,0)){
		log("SetHandleInformation error %d",GetLastError());
		return -1;
	}*/

	ERROR_IF_FAIL(::DuplicateHandle(GetCurrentProcess(),hOutReadTmp,
			GetCurrentProcess(),
			&hOutRead, // Address of new handle.
			0,FALSE, // Make it uninheritable.
			DUPLICATE_SAME_ACCESS));

	ERROR_IF_FAIL(::DuplicateHandle(GetCurrentProcess(),hErrReadTmp,
			GetCurrentProcess(),
			&hErrRead, // Address of new handle.
			0,FALSE, // Make it uninheritable.
			DUPLICATE_SAME_ACCESS));

	ERROR_IF_FAIL(::CloseHandle(hOutReadTmp));
	ERROR_IF_FAIL(::CloseHandle(hErrReadTmp));

	if(ctx->support_stdin){
		ERROR_IF_FAIL(::CreatePipe(&hInRead,&hInWriteTmp,&sa,0));
		ERROR_IF_FAIL(::DuplicateHandle(GetCurrentProcess(),hInWriteTmp,GetCurrentProcess(),&hInWrite,0,FALSE,DUPLICATE_SAME_ACCESS));
		ERROR_IF_FAIL(::CloseHandle(hInWriteTmp));
		ctx->h_stdin = hInWrite;//stdin for the child process
	}


	 STARTUPINFO si;
	 ZeroMemory(&(ctx->pi),sizeof(PROCESS_INFORMATION));
	 ZeroMemory(&si,sizeof(STARTUPINFO));

	 si.cb = sizeof(si);
	 si.hStdError = hErrWrite;
	 si.hStdOutput = hOutWrite;

	 si.hStdInput = hInRead?hInRead:(::GetStdHandle(STD_INPUT_HANDLE));
	 si.dwFlags |= STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;


	 std::string cmdline = ctx->program + " " + ctx->args;

	 std::string workdir = "C:\\c2v_demo\\tmp";
	 BOOL ret = ::CreateProcess(NULL,(LPSTR)cmdline.c_str(),NULL,NULL,TRUE,0,NULL,workdir.c_str(),&si,&(ctx->pi));//CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP
	 if(!ret){
		 log("CreateProcess failed %d",GetLastError());
		 return -1;
	 }

	 //handles that parent process has no need
	 ::CloseHandle(hOutWrite);
	 ::CloseHandle(hErrWrite);
	 ::CloseHandle(hInRead);

	 //wait process init
	 ::WaitForInputIdle(ctx->pi.hProcess,10*1000);

	 std::string out_line = "\0";
	 std::string err_line = "\0";

	 DWORD res = 0;

	 bool exited = false;

	 while(true){
		 
		 if(!exited){
			 DWORD ret = ::WaitForSingleObject(ctx->pi.hProcess,0);
			 if(ret == WAIT_OBJECT_0){
				 log("process exited");
				 ::GetExitCodeProcess(ctx->pi.hProcess,&res);
				exited = true;
				// loop = false;
			 }else if(ret == WAIT_TIMEOUT){

			 }else if(ret == WAIT_FAILED){
				 log("WaitForSingleObject failed:%d",GetLastError());
			 }
		 }

		 if(!ctx->parser) continue;

		 bool out_ret = process_output(hOutRead,out_line,ctx,0);

		 if(!out_ret){
			bool err_ret = process_output(hErrRead,err_line,ctx,1);
			if(!out_ret && !err_ret) break;
		 }
		 
	 }


	 if(ctx->parser) 
		ctx->parser->on_exit(ctx->thread_id,res);
	 ::CloseHandle(hOutRead);
	 ::CloseHandle(hErrRead);
	 ::CloseHandle(hInWrite);
	 ctx->h_stdin = 0;
	
	return res;
}




exec_id_t Executor::exec(char *exe,char *args,console_callback_t *parser,cparam_t param, bool support_stdin){

	DWORD thread_id = 0;

	exec_context_t *ctx = new exec_context_t();

	ctx->program = exe;
	ctx->args = args;
	ctx->param = param;
	//ctx->proc_id = 0;
	ctx->parser = parser;
	ctx->support_stdin = support_stdin;
	ctx->exited = false;


	HANDLE hThread = ::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ConsoleThread,ctx,0,&thread_id);
	if(hThread){
		ctx->h_thread = hThread;
		ctx->thread_id = thread_id;
		m_ctxs.push_back(ctx);
	}else{
		delete ctx;
		ctx = 0;
		log("CreateThread failed:%d",GetLastError());
	}

	return thread_id;
}

int Executor::pause(exec_id_t proc){

	DWORD res = 0;

	std::vector<exec_context_t*>::iterator it = m_ctxs.begin();
	while(it != m_ctxs.end()){
		if((*it)->thread_id == proc){
			res = ::SuspendThread((*it)->pi.hThread);
			if(res == -1){
				log("SuspendThread error %d",GetLastError());
			}
			break;
		}
		it++;
	}

	return res;
}


int Executor::stop(exec_id_t proc){
	DWORD res = 0;

	std::vector<exec_context_t*>::iterator it = m_ctxs.begin();
	while(it != m_ctxs.end()){
		if((*it)->thread_id == proc){

			if((*it)->exited)
			{
				log("already exited");
			}else{
				//::TerminateThread((*it)->h_thread,0);

				BOOL ret = ::AttachConsole((*it)->pi.dwProcessId);
				if(ret){
					ret = ::GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, (*it)->pi.dwProcessId);
					if(!ret){
						log("GenerateConsoleCtrlEvent error %d",GetLastError());
					}
					ret = ::FreeConsole();
				}

				res = ::WaitForSingleObject((*it)->pi.hProcess, 1000 * 5);
				if (res != WAIT_OBJECT_0)
					ret = ::TerminateProcess((*it)->pi.hProcess, -1);
				break;
			}
		}
		it++;
	}

	return res;
}




int Executor::resume(exec_id_t proc){


	DWORD res = 0;

	std::vector<exec_context_t*>::iterator it = m_ctxs.begin();
	while(it != m_ctxs.end()){
		if((*it)->thread_id == proc){
			res = ::ResumeThread((*it)->pi.hThread);
			break;
		}
		it++;
	}

	return res;
}


bool Executor::command(exec_id_t proc,const char *cmd){

	EnterCriticalSection(&cs_cmd);
	
	m_cmds
	LeaveCriticalSection(&cs_cmd);

	BOOL res = false;

	std::vector<exec_context_t*>::iterator it = m_ctxs.begin();
	while(it != m_ctxs.end()){
		if((*it)->thread_id == proc){

			assert((*it)->support_stdin);

			DWORD sz;
			res = ::WriteFile((*it)->h_stdin,cmd,strlen(cmd),&sz,NULL);
			break;
		}
		it++;
	}

	return res?true:false;
}